package com.basker.pisces.auth.domain.service;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.basker.pisces.auth.context.SimpleLoginContext;
import com.basker.pisces.auth.domain.entity.AuthUserEntity;
import com.basker.pisces.auth.domain.entity.command.AuthUserRegistCommand;
import com.basker.pisces.auth.domain.entity.command.ChangePasswordCommand;
import com.basker.pisces.auth.domain.exception.AuthUserRegisteredException;
import com.basker.pisces.auth.orm.AuthUser;
import com.basker.pisces.auth.service.pojo.ChangePasswordRequest;
import com.basker.pisces.auth.service.pojo.LoginRequest;
import com.basker.pisces.auth.service.pojo.LoginResult;
import com.basker.pisces.auth.service.pojo.RegistRequest;
import com.basker.pisces.config.ApplicationConfig;
import com.basker.pisces.context.ILoginContextManager;
import com.basker.pisces.context.LoginContext;
import com.basker.pisces.context.Token;
import com.basker.pisces.core.query.builder.CriterionBuilder;
import com.basker.pisces.core.repository.IBeanRepository;
import com.basker.pisces.domain.command.CommandResult;
import com.basker.pisces.domain.command.option.CommandExecuteOption;
import com.basker.pisces.domain.entity.data.BeanDataEntity;
import com.basker.pisces.domain.entity.data.IDataEntity;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.meta.PasswordProperty;
import com.basker.pisces.resource.StringResources;
import com.basker.pisces.service.exception.ServiceErrorResultException;

@Service
class AuthDomainServiceImpl implements IAuthDomainService {

    private static final String DEFAULT_INITIAL_PASSWORD_KEY = "pisces.auth.user.initial.password";

    private static final String DEFAULT_INITIAL_PASSWORD = "000000";

    @Autowired
    private ApplicationConfig appConfig;

    @Autowired
    private ILoginContextManager<LoginContext> manager;

    @Autowired
    private AuthUserEntity authUserEntity;

    @Autowired
    private IBeanRepository beanRepository;

    @Override
    public void changePassword(ChangePasswordRequest request) {
        ChangePasswordCommand changePasswordCommand = this.authUserEntity.getChangePasswordCommand();
        CommandExecuteOption option = this.authUserEntity.createCommandExecuteOption(changePasswordCommand);

        option.setDataObjectHandler((context, dataObjects) -> {
            Entity subEntity = context.getEntity();
            IDataEntity dataEntity = subEntity.createDataEntity();
            PasswordProperty passwordProperty = (PasswordProperty) subEntity.getProperty("Password");

            for (Object dataObject : dataObjects) {
                dataEntity.setDataObject(dataObject);

                String encryptInputValue = passwordProperty.encryptInputValue(request.getOldPassword());
                Object encrypPassword = dataEntity.getPropertyValue(passwordProperty).getDataValue();

                if (!encryptInputValue.equals(encrypPassword)) {
                    throw new ServiceErrorResultException(
                            StringResources.get("pisces-auth-service.changePassword-error-001"));
                }

                dataEntity.setPropertyValue(passwordProperty, request.getNewPassword());
            }
        });

        Long userId = request.getUserId();
        CommandResult commandResult = this.authUserEntity.executeCommand(changePasswordCommand, userId, option);

        if (!commandResult.isSuccess()) {
            throw commandResult.getCause();
        }
    }

    @Override
    public LoginContext identify(String tokenId) {
        return this.manager.get(new Token(tokenId));
    }

    @Override
    public LoginResult login(LoginRequest request) {
        String username = request.getUsername();
        String cellPhone = request.getCellPhone();
        String password = request.getPassword();

        CriterionBuilder[] builders = new CriterionBuilder[2];

        if (!StringUtils.isEmpty(username)) {
            builders[0] = CriterionBuilder.equal("Name", username);
        } else if (!StringUtils.isEmpty(cellPhone)) {
            builders[0] = CriterionBuilder.equal("CellPhone", cellPhone);
        } else {
            throw new ServiceErrorResultException(StringResources.get("pisces.auth.service.login-error-001"));
        }

        PasswordProperty property = (PasswordProperty) this.authUserEntity.getProperty("Password", true);
        builders[1] = CriterionBuilder.equal("Password", property.encryptInputValue(password));

        AuthUser user = this.beanRepository.queryBean(AuthUser.class, builders);
        if (user == null) {
            throw new ServiceErrorResultException(StringResources.get("pisces.auth.service.login-error-002"));
        }

        Token token = Token.newToken();
        this.storeToken(token, user);

        return new LoginResult(
                token,
                new LoginResult.User(
                        Objects.toString(user.getId()), user.getName(), user.getDisplayName()
                )
        );
    }

    @Override
    public void logout(String tokenId) {
        this.manager.remove(new Token(tokenId));
    }

    @Override
    public Long regist(RegistRequest request) throws AuthUserRegisteredException {
        String username = request.getUsername();
        String cellPhone = request.getCellPhone();
        String password = request.getPassword();
        password = StringUtils.isEmpty(password)
                ? appConfig.getString(DEFAULT_INITIAL_PASSWORD_KEY, DEFAULT_INITIAL_PASSWORD)
                : password;

        BeanDataEntity<AuthUser> dataEntity = authUserEntity.createDataEntity();
        AuthUser authUser = dataEntity.createDataObject();

        dataEntity.setPropertyValue("Name", username);
        dataEntity.setPropertyValue("CellPhone", cellPhone);
        dataEntity.setPropertyValue("Password", password);

        AuthUserRegistCommand registCommand = this.authUserEntity.getRegistCommand();
        CommandResult result = this.authUserEntity.executeCommand(registCommand, authUser);
        if (result.isSuccess()) {
            return authUser.getId();
        }

        throw result.getCause();
    }

    protected void storeToken(Token token, AuthUser user) {
        SimpleLoginContext lcx = new SimpleLoginContext();
        lcx.setUserId(user.getId());
        lcx.setToken(token);

        this.manager.put(token, lcx);
    }
}
